package util

//go:generate genny -in=ringbuffer_template.go -out=gen_ringbuffer.go gen "ChanValue=PubSubValue,*model.Room,*model.PlayerState,model.TimeDuration,*models.RoomListUpdatedRes"

import (
	"sync"

	"github.com/cheekybits/genny/generic"
)

// Ringbuffer is use in channel
// Inspired by https://content.pivotal.io/blog/a-channel-based-ring-buffer-in-go
// Code is from https://github.com/cloudfoundry/loggregator/blob/40fe56ee599cdd17510efc0cab7718f27958268c/server/src/loggregator/ringbuffer/ring_buffer.go

// ChanValue reprents the value transfered by channel
type ChanValue generic.Type

// ChanValueRingBuffer simulates ringbuffer to avoid
// block in main thread
type ChanValueRingBuffer struct {
	inputChannel  chan ChanValue
	outputChannel chan ChanValue
	closeOnce     sync.Once
}

// NewChanValueRingBuffer creates ChanValue buffer
// !! Memory Leaking Warning (if ignore the out)
func NewChanValueRingBuffer() *ChanValueRingBuffer {
	rb := &ChanValueRingBuffer{
		inputChannel:  make(chan ChanValue, 1),
		outputChannel: make(chan ChanValue, 1),
	}
	go rb.run()
	return rb
}

// Send send value to in channel
func (r *ChanValueRingBuffer) Send(value ChanValue) {
	r.inputChannel <- value
}

// GetOutputChannel returns the output channel
func (r *ChanValueRingBuffer) GetOutputChannel() <-chan ChanValue {
	return r.outputChannel
}

// run runs goroutine to handle ringbuffer
func (r *ChanValueRingBuffer) run() {
	for v := range r.inputChannel {
		select {
		case r.outputChannel <- v:
		default:
			<-r.outputChannel
			r.outputChannel <- v
		}
	}
	close(r.outputChannel)
}

// Close close the input channel, recover mem
func (r *ChanValueRingBuffer) Close() {
	r.closeOnce.Do(func() {
		close(r.inputChannel)
	})
}
